// ***********************************************************
//  CUtil.cpp - utility class containing static functions
//
//  REVISION  DATE      AUTHOR			MODIFICATION
//  Rev-1     Spring07  CPA				Original author, CSSE
//  Rev-2     9/13/08   VN              Modified method int FindStringsCaseInsensitive
//                                        to fix detect in case %> is used in HTML
//                                        e.g., "width=100%>"
//                                      Added methods FindCharAvoidEscape(), ListFiles()
//  Rev-3     8/20/09   VN              Quoted Windows' folder name to fix a defect related to
//                                       listing a folder that has spaces.
// ***********************************************************

#include "CUtil.h"
#include <algorithm>
#include <string>
#include <time.h>
#include <sstream>

#ifdef UNIX
#else
#include <io.h>
#endif

/**********************************************************************
RemoveBlanks()
returns a string without spaces or tabs
modified 07/06/07: do not remove the blank between words.
**********************************************************************/
string CUtil::TrimString(string str)
{
	size_t idx = str.find_first_not_of(" \t\n\r\f");
	if (idx != string::npos)
	{
		str = str.substr(idx);
	}
	idx = str.find_last_not_of(" \t\n\r\f");
	if (idx != string::npos)
	{
		str = str.substr(0,idx+1);
	}
	return str;

} // end RemoveBlanks()


/**********************************************************************
to_lower()
returns a string in lower case
**********************************************************************/
string CUtil::to_lower(string string_to_lower)
{
	for (int i = 0; i < (int)string_to_lower.size(); i++) 
	{
		string_to_lower[i] = tolower(string_to_lower[i]);
    }
	return string_to_lower;
}

/************************************************************************
check_blank()
 This Procedure checks weather the line passed is a blank or not
************************************************************************/
bool CUtil::CheckBlank(string str) 
{
	string::size_type idx;
	idx = str.find_first_not_of("\n\t\r\f ");	//searchs for chars other than specified
	if (idx != string::npos)
	{
		return false;
	}
	return true;
} // end check_blank()


/************************************************************************
count_tally()
search string $base and update $count accordingly
count is incremented by number of  occurences of keywords 
contained in $set[] within string $base
base : string to search
set : set of keywords
setnum : number of entries in $set
mode : 1 if 'exclude' mode, 
       2 if 'include1', 'include2' mode.
exclude: keywords shouldn't be surrounded by characters in $exclude to
	     be counted as valid
include1: keywords can only be preceded by characters in this string
include2: keywords can only be followed by characters in this string
************************************************************************/
//void CCodeCounter::count_tally(string base, string set[], int setnum, int &count,int mode, string exclude, string include1,string include2)
//{
//	string::size_type idx;
//	if(mode == 1)
//	{ // exclude mode
//		for (int i=0; i<setnum; i++)
//		{
//			idx = base.find(set[i]);
//			while(idx != string::npos)
//			{
//				if((exclude.find(base[idx+set[i].size()] ) == string::npos) && (exclude.find(base[idx-1]) == string::npos))
//				{
//					count++; 
//				} // end if
//				idx = base.find(set[i], idx+set[i].size());
//			} // end while
//        } // end for 
//    } // end if
//    else if (mode == 2)
//	{ // include mode
//		for(int j=0 ; j<setnum; j++)
//		{
//			idx = base.find(set[j]);
//			while(idx != string::npos)
//			{
//				if((include1.find(base[idx-1]) != string::npos) &&(include2.find(base[idx+set[j].size()]) != string::npos)  )
//				{
//					count++;
//			    } //end if
//				idx = base.find(set[j], idx+set[j].size());
//		    } //end while
//		} // end for
//	} //end else if
//} // end count_tally() 

// New count_tally
// set: string[] => vector<string>
// search string $base and update $count accordingly
// $count is incremented by number of  occurences of keywords 
// contained in $set[] within string $base
// $base : string to search
// $set : set of keywords
// $setnum : number of entries in $set
// $mode : 1 if 'exclude' mode, 
//		   2 if 'include1', 'include2' mode.
// $exclude: keywords shouldn't be surrounded by characters in $exclude to
// 			be counted as valid
// $include1: keywords can only be preceded by characters in this string
// $include2: keywords can only be followed by characters in this string
//void count_tally(string base, vector<string> &set, int setnum, long int &count, 
//casesensitive : if your language module is casesensitive then true else false
void CUtil::CountTally(string base, StringVector& container, int &count, int mode, string exclude,
					   string include1, string include2, IntPairVector* counter_container, bool dup, bool casesensitive)
{
	base = " "+base+" ";
	string::size_type idx;
	string base1;
	StringVector::iterator vit;
	IntPairVector::iterator cit;
	int single_count = 0;

	if (counter_container)
	{
		cit = counter_container->begin();
	}	

	if (casesensitive == false)
	{
		base1 = CUtil::to_lower(base);
		for(vit=container.begin(); vit != container.end(); vit++)
			(*vit) = CUtil::to_lower((*vit));
	}
	else
		base1=base;	
	
	if (mode == 1)
	{	// exclude mode
		for(vit=container.begin(); vit != container.end(); vit++)
		{
			idx = base1.find((*vit));
			while (idx != string::npos)
			{
				if ((exclude.find(base1[idx+((*vit).size())]) == string::npos) &&
					(exclude.find(base1[idx-1]) == string::npos))
				{
					count++;
					single_count++;
				}
				idx = base1.find((*vit), idx+((*vit).size()));
			}
			if (counter_container)
			{
				if (dup)
					(*cit).second += single_count;
				else
					(*cit).first += single_count;
				single_count = 0;
				cit++;
			}
		}
	}
	else if (mode == 2)
	{	// include mode
		for(vit=container.begin(); vit != container.end(); vit++) 
		{
			idx = base1.find((*vit));
			while (idx != string::npos) 
			{
				if ((include1.find(base1[idx-1]) != string::npos) &&
					(include2.find(base1[idx+((*vit).size())]) != string::npos))
				{
					count++;
				}
				idx = base1.find((*vit), idx+((*vit).size()));
			}
		}
	}
}

bool CUtil::isInteger(string str)
{
	str = TrimString(str);
	if (str.find_first_not_of("0123456789") != string::npos)
	{
		return false;
	}
	else
	{
		return true;
	}
}

/////////////////////////////////////////////////////////////////////////
//CUtil::FindStringsCaseInsensitive
//Find the first appearance of each element of table's first value.
//return value is the second value of the map
//the position is size_t& pos
/////////////////////////////////////////////////////////////////////////
size_t CUtil::FindStringsCaseInsensitive(string target, map<string,int>& table, size_t& pos, size_t preLang) //Mod042909AS
{
	target = to_lower(target);
	size_t tmp_pos;
	pos = string::npos;
	size_t ret = INVALID_POSITION; //Mod042909AS
	for (map<string,int>::iterator iter = table.begin(); iter != table.end(); iter++)
	{
		tmp_pos = target.find(iter->first);
		if (tmp_pos != string::npos && (pos == string::npos || pos > tmp_pos))
		{
			// Fixed defect of case "width=100%>"
			ret = iter->second;
			if ((ret == WEB_ASP_JSP_END && preLang != WEB_ASP_JSP_START) ||
				(ret == WEB_PHP_END && (preLang != WEB_PHP_START && preLang != WEB_PHP_START2)))
				ret = INVALID_POSITION; //Mod042909AS

			// End Fixed defect of case "width=100%>"
			pos = tmp_pos;
		}
	}
	return ret;
}

// Method: FindCharAvoidEscape(...)
// Description: find the position of the "target" char in "source" string
//              starting from "start_idx" but ignoring escape chars.
//              for example, '\n' is different from 'n' in "this is not nothing"
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     8/9/08   VN      Original author
size_t CUtil::FindCharAvoidEscape(string source, char target, size_t start_idx, char escape) 
{
	size_t idx, i;
	if (start_idx >= source.length()) return string::npos;
	idx = start_idx;
	while (idx < source.length()) 
	{
		idx = source.find(target, idx);
		if (idx == string::npos) return idx;
		
		for (i = 1; i <= idx; i ++) 
		{
			// trace back to the previous char ex. "adfd\\\\"
			if (source[idx - i] != escape) break; 
		}
		if (i % 2 != 0) break; // case \\", avoid case something\"
		
		idx ++;
	}
	return idx;
}

// Method: ListFiles(...)
// Description: list all files specified by the fileExt in the folder 
//              and its sub-folders to store in the fileList vector
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     8/9/08   VN      Original author
#ifndef UNIX
void CUtil::ListFiles(string &folder, string &fileExt, vectorString &fileList) 
{
	struct _finddata_t ext_file;
	intptr_t hFile;
	if ((hFile = _findfirst((folder + fileExt).c_str(), &ext_file)) == -1L) return;
	do
	{
		fileList.push_back(folder + ext_file.name);
	} while(_findnext(hFile, &ext_file) == 0);
	_findclose(hFile);
}
#endif

////
// ------------------   Duplication Modification -----------------------
// For a given directory name. Extract all the files from that directory as well as 
// from all its sub-directories and store the filenames in the fileList vector.
// Changes are applicable only to the Windows OS currently but support would be added 
// for all platforms herein in the next code release deadline.
// Revision History      Author       Modification
// 8/20/09               VN           Quoted folder path if it contains spaces
// 9/01/09               VN           Fixed the defect when the folder path includes filename
//                                     in this case, there is no file listed as \ character is
//                                     added to the folder path. 
void CUtil::ListAllFiles(string &folder, vectorString &fileExtList, vectorString &fileList) 
{
	string command = "";
	folder = CUtil::TrimString(folder);
	if (folder.find(" ") != string::npos && folder.find_first_of("\"") != 0)
	{	// the folder string is not quoted
		folder = "\"" + folder + "\""; // quote it
	}

#ifdef UNIX
	folder = folder + "/";
	command.append("find " + folder + "  \\( ");

	command.append("-name \"" + fileExtList.at(0) + "\"" );

    // Generate the string to containing the names of all file extensions to be searched
	for (size_t i=1; i < fileExtList.size(); i++)
	{
		command.append( "  -o -name \"" + fileExtList.at(i) + "\"");
	}

	command.append(" \\)");
#else
	command.append("dir /b /s");

	// Generate the string to containing the names of all file extensions to be searched

	folder = folder + "\\";
	for (size_t i=0; i < fileExtList.size(); i++)
	{
		command.append(" ");
		command.append(folder + fileExtList.at(i));
	}
#endif

	command.append(" > ___temp.dat");

	// Run the system command as mentioned below redirecting its output to ___temp.dat:
	// dir /b /s <dirnameA>/*.cpp <dirnameA>/*.java > ___temp.dat
	// to extract all the files with file extension cpp and java in BaseLine dirnameA.
	system(command.c_str());

	// Read through ___temp.dat and get the names of all the files in the directory mentioned.
	ifstream fr("___temp.dat", ios::in);
	if (!fr.is_open())
	{
		printf("Could not open ___temp.dat\n");
		exit(0);
	}
	else
	{
		string oneline;
		getline(fr,oneline);
		while (!fr.eof())
		{
			size_t pos = oneline.find(folder);
			// Absolute path names of files are stored in the ___temp.dat. So make the names
			// of all files relative to the current working directory path.
			if (pos != string::npos && folder.find(".") != 0)
			{	// avoid the folder having the first char of . (current/parent)
				oneline = oneline.substr(pos);
			}
			fileList.push_back(oneline);
			getline(fr,oneline);
		}
		fr.close();
		// Delete the temporary file created to store the results.
#ifdef UNIX
		system("rm ___temp.dat");
#else
		system("del ___temp.dat");
#endif
	}
}

// Description: find the keyword in the string starting from start 
//              to end with casesensitive
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     8/9/08   VN      Original author
size_t CUtil::FindKeyword(string &str, string keyword, size_t start, size_t end, bool casesensitive) 
{
#define SPECIAL_CHARS	" \t;[]()+/-*<>=,&~!^?:%{}|"
	size_t kw_length = keyword.length();
	size_t idx, i = start;
	if (end == TO_END_OF_STRING) end = str.length() - 1; // inclusive. Mod042909AS

	if (!casesensitive)
	{
		str = CUtil::to_lower(str);
		keyword = CUtil::to_lower(keyword);
	}

	while (i <= end)
	{
		idx = str.find(keyword, i);
		if (idx != string::npos && idx + kw_length - 1 <= end)
		{
			if ((idx == 0 || strchr(SPECIAL_CHARS, str[idx - 1]) != NULL)
				&& (idx + kw_length >= str.length()
				|| strchr(SPECIAL_CHARS, str[idx + kw_length]) != NULL))
			{
				return idx; //the keyword stands alone or surrounded by special chars
			}
		}
		else
		{
			idx = string::npos; //cannot find the keyword in str
			break;
		}
		i = idx + 1; //keyword found but not stands alone nor surrounded by special chars
	}

	return string::npos; //not found
#undef SPECIAL_CHARS
}

// Description: Extract the filename (without path) from the filepath
//              ex. abc\xyz.cpp --> xyz.abc
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     Fall 08   VN      Original author
string CUtil::ExtractFilename(string const &filepath) 
{
	size_t idx = filepath.find_last_of("\\");
	if (idx == string::npos) idx = filepath.find_last_of("/"); // Unix
	if (idx != string::npos) return filepath.substr(idx + 1);
	return filepath;
}

int CUtil::PrintFileHeader(ofstream &pout, string const title)
{
	int lineLength = 100;
	int mysize;
	string myOutput;

	time_t myTime;
	struct tm* myLocalTime;
	time(&myTime);
	myLocalTime = localtime(&myTime);

	myOutput = "USC Unified CodeCount (UCC)";
	mysize = (int)myOutput.length();
	for (int i = 0; i < (lineLength - mysize) / 2; i++)
	{
		myOutput.insert(0," ");
		myOutput.append(" ");
	}
	pout << myOutput << endl;

	myOutput = "(c) Copyright 1998 - 2009 University of Southern California";
	mysize = (int)myOutput.length();
	for (int i = 0; i < (lineLength - mysize) / 2; i++)
	{
		myOutput.insert(0, " ");
		myOutput.append(" ");
	}

	pout << myOutput << endl << endl;
	myOutput = title;
	mysize = (int)myOutput.length();
	for (int i = 0; i < (lineLength - mysize) / 2; i++)
	{
		myOutput.insert(0," ");
		myOutput.append(" ");
	}
	pout << myOutput << endl;

	ostringstream s0;
	s0 << "Generated by UCC v." << PRODUCT_REVISION << " on " << myLocalTime->tm_mon + 1
		<< " " << myLocalTime->tm_mday << " " << myLocalTime->tm_year + 1900;

	myOutput = s0.str();
	mysize = (int)myOutput.length();
	for (int i = 0; i < (lineLength - mysize) / 2; i++)
	{
		myOutput.insert(0," ");
		myOutput.append(" ");
	}

	pout << myOutput << endl << endl;

	return 1;
}

// Description: removes extra text added after @@ by ClearCase
// The original implementation was provided by NGC
// Modification: Mod052409_1VN
string CUtil::ConvertClearCaseFile(string fileName)
{
    size_t loc;

    //Remove @@ and anything after
	loc = fileName.find_last_of("@@");
	if (loc == string::npos)
	{
		return fileName;
	}
	else
	{
         return fileName.erase(loc);
    }
}

void CUtil::AddError(string err)
{
	static ofstream errFile;
	static char *s;

	// open the error file if not already opened
	if (!s)
	{
		time_t myTime;
		struct tm *myLocalTime;
		time(&myTime);
		myLocalTime = localtime(&myTime);
		s = new char[50];
		strftime(s, 50, "error_log_%m%d%Y_%I%M%S.txt", myLocalTime);

		errFile.open(s, ofstream::app);
		if (! errFile.is_open())
		{
			cerr << "Error: Failed to open error log file" << endl;
			return;
		}
	}

	errFile << err << endl;
	return;
}

//Checks for line truncation
//returns the portion of the string to keep after truncation
size_t CUtil::TruncateLine(size_t length, size_t totalLength, size_t truncate, bool &trunc_flag)
{
	if (truncate == 0)
	{
		trunc_flag = false;
		return(length);
	}
	else if (totalLength >= truncate)
	{
		trunc_flag = true;
		return(0);
	}
	else if (totalLength + length <= truncate)
	{
		trunc_flag = false;
		return(length);
	}
	else
	{
		trunc_flag = true;
		return(truncate - totalLength);
	}
}
